// Serialize Stream
// (c) jimon game studio

#ifndef JEH_CSerializeSTREAM
#define JEH_CSerializeSTREAM

#include "ISerializeStream.h"
#include <malloc.h>

namespace je
{
	namespace core
	{
		//! Serialize Stream
		class CSerializeStream:public je::core::ISerializeStream
		{
		protected:
			//! Serialize Stream Variable
			struct jeSerializeStreamVariable
			{
				//! Current Hash
				MappingHashType CurrentHash;

				//! Current Size
				u32 CurrentSize;

				//! Global Offset
				u32 GlobalOffset;
			};

			//! Serialize Stream Section
			class jeSerializeStreamSection
			{
			protected:
				//! Current Hash
				MappingHashType CurrentHash;

				//! Serialize Stream Variables Array Type
				typedef jeDynamicArray<jeSerializeStreamVariable> SerializeStreamVariablesArrayType;

				//! Variables Array
				SerializeStreamVariablesArrayType VariablesArray;

				//! Current Data
				c8 * CurrentData;

				//! Current Data Size
				u32 CurrentDataSize;

				//! Current Parent
				jeSerializeStreamSection * CurrentParent;

				//! Current Parent Id
				u32 CurrentParentId;

				//! Serialize Stream Sections Array Type
				typedef jeDynamicArray<jeSerializeStreamSection*> SerializeStreamSectionsArrayType;

				//! Sections Array
				SerializeStreamSectionsArrayType SectionsArray;

				//! Current Linear Mode
				u1 CurrentLinearMode;

				//! Current Linear Mode Reading Value Id
				u32 CurrentLinearModeReadingValueId;

				//! Current Linear Mode Reading Section Id
				u32 CurrentLinearModeReadingSectionId;
			public:
				//! Constructor
				jeSerializeStreamSection(const MappingHashType & Hash,jeSerializeStreamSection * Parent,u32 ParentId)
					:CurrentHash(Hash),CurrentData(NULL),CurrentDataSize(0),CurrentParent(Parent),CurrentParentId(ParentId),CurrentLinearMode(false),CurrentLinearModeReadingValueId(0),CurrentLinearModeReadingSectionId(0)
				{
				}

				//! Destructor
				~jeSerializeStreamSection()
				{
					if(CurrentData)
						free(CurrentData);

					VariablesArray.Clear();

					for(u32 i=0;i<SectionsArray.GetSize();i++)
						JEDELETE(SectionsArray[i]);
					SectionsArray.Clear();
				}

				//! Get Hash
				inline const MappingHashType & GetHash()
				{
					return CurrentHash;
				}

				//! Get Parent
				inline jeSerializeStreamSection * GetParent()
				{
					return CurrentParent;
				}

				//! Get Parent Id
				inline u32 GetParentId()
				{
					return CurrentParentId;
				}

				//! Get Data
				inline c8 * GetData()
				{
					return CurrentData;
				}

				//! Set Variable
				void SetVariable(const MappingHashType & Hash,void * Value,u32 Size)
				{
					VariablesArray.InsertBlock();

					jeSerializeStreamVariable & Variable = VariablesArray.GetLast();

					Variable.CurrentHash = Hash;
					Variable.CurrentSize = Size;
					Variable.GlobalOffset = CurrentDataSize;

					if(CurrentDataSize)
						CurrentData = reinterpret_cast<c8*>(realloc(CurrentData,CurrentDataSize+Size));
					else
						CurrentData = reinterpret_cast<c8*>(malloc(Size));

					memcpy(CurrentData+CurrentDataSize,Value,Size);

					CurrentDataSize += Size;
				}

				//! Get Variable
				inline u1 GetVariable(const MappingHashType & Hash,jeSerializeStreamVariable & Variable)
				{
					if(CurrentLinearMode)
					{
						Variable = VariablesArray[CurrentLinearModeReadingValueId++];
						return true;
					}
					else
					{
						for(u32 i=0;i<VariablesArray.GetSize();i++)
							if(VariablesArray[i].CurrentHash == Hash)
							{
								Variable = VariablesArray[i];
								return true;
							}
						return false;
					}
				}

				//! Get Variable Data
				inline void * GetVariableData(const MappingHashType & Hash)
				{
					jeSerializeStreamVariable Variable;
					if(GetVariable(Hash,Variable))
						return CurrentData+Variable.GlobalOffset;
					else
						return NULL;
				}

				//! Open Section
				inline jeSerializeStreamSection * OpenSection(const MappingHashType & Hash,u1 NewSection = false)
				{
					if(CurrentLinearMode && (!NewSection))
						return SectionsArray[CurrentLinearModeReadingSectionId++];
					else
					{
						jeSerializeStreamSection * Section = NULL;
						JENEW(Section,jeSerializeStreamSection(Hash,this,SectionsArray.GetSize()))
						SectionsArray.InsertElement(Section);
						return Section;
					}
				}
				
				//! Next Section
				inline jeSerializeStreamSection * NextSection(jeSerializeStreamSection * Section)
				{
					u32 ParentId = Section->GetParentId()+1;
					if(ParentId < SectionsArray.GetSize())
						return SectionsArray[ParentId];
					else
						return NULL;
				}

				//! Close Section
				inline jeSerializeStreamSection * CloseSection()
				{
					return CurrentParent;
				}

				//! Set Linear Mode
				void SetLinearMode(u1 LinearMode)
				{
					CurrentLinearMode = LinearMode;
					for(u32 i=0;i<SectionsArray.GetSize();i++)
						SectionsArray[i]->SetLinearMode(LinearMode);
				}

				//! Flush Linear Reading
				void FlushLinearReading()
				{
					CurrentLinearModeReadingValueId = 0;
					CurrentLinearModeReadingSectionId = 0;
					for(u32 i=0;i<SectionsArray.GetSize();i++)
						SectionsArray[i]->FlushLinearReading();
				}

				//! Load From Buffer
				void LoadFromBuffer(IBuffer * Buffer)
				{
					if(CurrentData)
						free(CurrentData);
					VariablesArray.Clear();
					SectionsArray.Clear();

					Buffer->Read(&CurrentHash,sizeof(MappingHashType));

					u32 VariablesArraySize;
					Buffer->Read(&VariablesArraySize,sizeof(u32));
					if(VariablesArraySize)
						VariablesArray.CopyFrom(Buffer,VariablesArraySize*sizeof(jeSerializeStreamVariable));

					Buffer->Read(&CurrentDataSize,sizeof(u32));
					if(CurrentDataSize)
					{
						CurrentData = reinterpret_cast<c8*>(malloc(CurrentDataSize));
						Buffer->Read(CurrentData,CurrentDataSize);
					}

					u32 SectionArraySize;
					Buffer->Read(&SectionArraySize,sizeof(u32));
					for(u32 i=0;i<SectionArraySize;i++)
					{
						jeSerializeStreamSection * Section = NULL;
						JENEW(Section,jeSerializeStreamSection(0,this,SectionsArray.GetSize()))
						SectionsArray.InsertElement(Section);
						Section->LoadFromBuffer(Buffer);
					}
				}

				//! Save To Buffer
				void SaveToBuffer(IBuffer * Buffer)
				{
					Buffer->Write(&CurrentHash,sizeof(MappingHashType));

					u32 VariablesArraySize = VariablesArray.GetSize();
					Buffer->Write(&VariablesArraySize,sizeof(u32));
					if(VariablesArraySize)
						Buffer->Write(VariablesArray.GetPtr(),VariablesArraySize*sizeof(jeSerializeStreamVariable));

					Buffer->Write(&CurrentDataSize,sizeof(u32));
					if(CurrentDataSize)
						Buffer->Write(CurrentData,CurrentDataSize);

					u32 SectionArraySize = SectionsArray.GetSize();
					Buffer->Write(&SectionArraySize,sizeof(u32));
					for(u32 i=0;i<SectionArraySize;i++)
						SectionsArray[i]->SaveToBuffer(Buffer);
				}

				//! Get Buffer Size
				u32 GetBufferSize()
				{
					u32 Size = sizeof(MappingHashType) + 4 + VariablesArray.GetSize() * sizeof(jeSerializeStreamVariable) + 4 + CurrentDataSize + 4;
					for(u32 i=0;i<SectionsArray.GetSize();i++)
						Size+=SectionsArray[i]->GetBufferSize();
					return Size;
				}
			};

			//! Current Section
			jeSerializeStreamSection * CurrentSection;

			//! Current Stream Mode
			SerializeStreamMode CurrentStreamMode;

			//! Current Linear Mode
			u1 CurrentLinearMode;
		public:
			//! Constructor
			CSerializeStream();

			//! Destructor
			~CSerializeStream();

			//! Set Mode
			void SetMode(SerializeStreamMode StreamMode);

			//! Get Mode
			SerializeStreamMode GetMode();

			//! Set Linear Mode
			void SetLinearMode(u1 Enabled);

			//! Get Linear Mode
			u1 GetLinearMode();

			//! New Section
			void NewSection(const MappingHashType & Hash);

			//! Next Section
			u1 NextSection();

			//! Open Section
			void OpenSection(const MappingHashType & Hash);

			//! Close Section
			void CloseSection();

			//! Work With Value
			u1 WorkWithValue(const MappingHashType & Hash,void * Value,u32 Size);

			//! Work With Value
			u1 WorkWithValue(const MappingHashType & Hash,void ** Value,u32 * Size);

			//! Load from buffer
			void LoadBuffer(IBuffer * Buffer);

			//! Save to buffer
			void SaveBuffer(IBuffer * Buffer);

			//! Flush Linear Reading
			void FlushLinearReading();
		};
	}
}

#endif
